/******************************************************************
 * This program was written as part of
 *   Assignment 4
 *   Computer Graphics                                             
 *   Semester 1, 2013
 *   School of Computer Science,
 *   The University of Adelaide
 *                                                                          
 * I declare that all material in this assessment is my own work except where
 * there is clear acknowledgement and reference to the work of others. I have
 * read the University Policy Statement on Plagiarism, Collusion and Related
 * Forms of Cheating:  

 * http://www.adelaide.edu.au/policies/?230

 * I give permission for my assessment work, including my subversion
 * repository, to be reproduced and submitted to academic staff for the
 * purposes of assessment and to be copied, submitted and retained in a form
 * suitable for electronic checking of plagiarism.  
                            
 * Authors:
 *  Terry Moschou    a1208955
 *  Salih Al-Rashid  a1210108
 *  Ben McAleer      a1212203
*******************************************************************/

#include "Terrain.hpp"
#include "Model.hpp"
#include "Context.hpp"
#include "Scene.hpp"
#include "Transform.hpp"
#include "objLoader.h"
#include <glm/gtx/normal.hpp>
#include <math.h>

/**
 * 
 *  +--+--+--+--+
 *  | /| /| /| /|
 *  |/ |/ |/ |/ |
 *  +--+--+--+--+    
 *  | /| /| /| /|    |
 *  |/ |/ |/ |/ |    vertical (rows)
 *  +--+--+--+--+    |
 *  | /| /| /| /|    v
 *  |/ |/ |/ |/ |
 *  +--+--+--+--+
 * 
 *   -- horizontal (columns) -->
 *  
 */

int Terrain::offset(int horizontal, int vertical) {
  return vertical*(m_cols + 1) + horizontal;
}

Terrain::Terrain(Object * parent, Model * model, int rows, int cols)
  : Object(parent, mat4(1.0f), model, "Terrain"),
    m_rows(rows), m_cols(cols)
{
  
  m_terrainArray = new float [(m_cols + 1) * (m_rows + 1)];
  
  for (int i = 0; i < m_model->m_objLoader->vertexCount; ++i) {
    int x = m_model->m_objLoader->vertexList[i]->e[0]/2;
    int z = m_model->m_objLoader->vertexList[i]->e[2]/2;
    if(x>=0 && z>=0 && x<=m_cols && z<=m_rows)
      m_terrainArray[offset(x,z)] = m_model->m_objLoader->vertexList[i]->e[1];
  }
  
  goforit = false;

}

Object* Terrain::near(vec3 pos, const std::string & objname) {
  
  Object * root = dynamic_cast<Object*>(g_context.m_scene);
   
  for (Iterator itr = root->begin(); itr != root->end(); ++itr)
  {
    Object * obj = *itr;
    if(obj->m_model && obj->getName().find(objname) != std::string::npos) {
      Box b = obj->m_model->m_bounds;
      Transform tr(obj->getWorldModelMatrix());
      b.pos += tr.getTranslation();
      b.dis += vec3(1.0f,1.0f,1.0f);
      if(glm::all(glm::greaterThan(pos, b.min())) && 
         glm::all(   glm::lessThan(pos, b.max()))     ) {
        return obj;
      }
    }
  }
  return 0;
}

bool Terrain::free(vec3 pos) {

  if(pos.x<1.0f || pos.z<1.0f || pos.x>127.0f || pos.z>127.0f)
    return false;
  
  Object * root = dynamic_cast<Object*>(g_context.m_scene);
   
  
  for (Iterator itr = root->begin(); itr != root->end(); ++itr)
  {
    Object * obj = *itr;

    if(obj->getName().find("Player") != std::string::npos) {
      continue;
    }
    if(obj->getName().find("Segway") != std::string::npos) {
      continue;
    }
    if(obj->getName().find("Terrain") != std::string::npos) {
      continue;
    }
    if(obj->getName().find("Equipment") != std::string::npos) {
      continue;
    }
    if(obj->m_model) {
      Box b = obj->m_model->m_bounds;
      
      Transform tr(obj->getWorldModelMatrix());
      b.pos += tr.getTranslation();
      if(glm::all(glm::greaterThan(pos, b.min())) && 
         glm::all(   glm::lessThan(pos, b.max()))     ) {
        m_box = b;
        m_collision = obj;
        return false;
      }
    }
  }
  return true;
}

float Terrain::getHeight(float x, float z) {

  if(x/2.0f>m_cols || z/2.0f>m_rows || x<0.0f || z<0.0f)
    return 0.0f;
  
  //corner of the square not in the trianglulated face
  int excludedvert;

  //determining the square coordinates
  int xd = floor(x/2.0)*2;
  int zd = floor(z/2.0)*2;
  int xu = xd+2;
  int zu = zd+2;

  //float xa = xu-x/2.0f;
  //float za = zu-z/2.0f;
  
  float xa = x - xd;
  float za = z - zd;
  
  //determining the triangle
  if(((xd+zd)/2)%2 == 1) {
    if(xa >= za) {
      excludedvert = 3;
    } else {
      excludedvert = 1;
    }
  } else {
    if(2.0f > xa + za) {
      excludedvert = 2;
    } else {
      excludedvert = 0;
    }
  }

  vec3 hs[4];
  hs[0] = vec3(xd, m_terrainArray[offset(xd/2,zd/2)],zd); //0
  hs[3] = vec3(xd, m_terrainArray[offset(xd/2,zu/2)],zu); //3
  hs[2] = vec3(xu, m_terrainArray[offset(xu/2,zu/2)],zu); //2
  hs[1] = vec3(xu, m_terrainArray[offset(xu/2,zd/2)],zd); //1
  
  vec3 normal;
  float d;
  switch(excludedvert) {
    case 0: normal = glm::triangleNormal(hs[1], hs[2], hs[3]); break;
    case 1: normal = glm::triangleNormal(hs[0], hs[2], hs[3]); break;
    case 2: normal = glm::triangleNormal(hs[0], hs[1], hs[3]); break;
    case 3: normal = glm::triangleNormal(hs[0], hs[1], hs[2]); break;
  }
  
  //no reference compadre
  if(excludedvert == 0)
    d = normal.x*hs[1].x +
        normal.y*hs[1].y + 
        normal.z*hs[1].z ;
  else
    d = normal.x*hs[0].x +
        normal.y*hs[0].y +
        normal.z*hs[0].z ;
  
  d -= normal.x*x+normal.z*z;
  
  float y = d/normal.y;
  
  if(goforit) {
    if(g_context.getPlayer()->m_segway) {
//       if(g_context.getSegway()->m_pos.y+0.5 >= m_box.max().y+g_context.getSegway()->m_vspeed)
//       for(float ox = -1.1; ox < 1.2; ox = ox + 1.1)
//         for(float oz = -1.1; oz < 1.2; oz = oz + 1.1)
//           if(!free(vec3(x+ox,y,z+oz))) {
//             y = m_box.max().y;
//             g_context.getSegway()->m_vspeed = 0.5f;
//             g_context.getSegway()->m_grounded = false;
//           }
    } else {
      if(g_context.getPlayer()->m_pos.y >= m_box.max().y+g_context.getPlayer()->m_speed)
      for(float ox = -0.8; ox < 0.9; ox = ox + 0.8)
        for(float oz = -0.8; oz < 0.9; oz = oz + 0.8)
          if(!free(vec3(x+ox,y,z+oz)))
            y = m_box.max().y;
    }
  }
  
  return y;

}